/*
 * 用户认证服务
 * 位于包：org.example.lethear.service
 * 
 * 这个类是用户认证系统的核心业务逻辑处理类，负责：
 * 1. 用户注册业务逻辑
 * 2. 用户登录业务逻辑
 * 3. JWT令牌刷新业务逻辑
 * 4. 用户密码修改业务逻辑
 * 5. 用户名和邮箱可用性验证
 * 6. 认证响应的构建
 * 
 * 这个类与控制器层和数据访问层协作，实现完整的用户认证功能。
 */
package org.example.lethear.service;

import lombok.RequiredArgsConstructor;

import org.example.lethear.dto.auth.*;
import org.example.lethear.entity.Role;
import org.example.lethear.entity.User;
import org.example.lethear.entity.UserProfile;
import org.example.lethear.exception.BusinessException;
import org.example.lethear.repository.RoleRepository;
import org.example.lethear.repository.UserRepository;
import org.example.lethear.repository.UserProfileRepository;
import org.example.lethear.security.UserDetailsImpl;
import org.example.lethear.security.UserDetailsServiceImpl;
import org.example.lethear.utils.JwtUtils;
import org.example.lethear.utils.SecurityUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 用户认证服务
 * 
 * 使用@Service注解标识这是一个Spring服务组件
 * 使用@RequiredArgsConstructor自动生成构造函数注入依赖
 */
@Service
@RequiredArgsConstructor
public class AuthService {
    
    // 用户数据访问对象，用于用户信息的CRUD操作
    private final UserRepository userRepository;
    
    // 用户资料数据访问对象，用于用户资料的CRUD操作
    private final UserProfileRepository userProfileRepository;
    
    // 角色数据访问对象，用于角色信息的CRUD操作
    private final RoleRepository roleRepository;
    
    // 密码编码器，用于密码的加密和验证
    private final PasswordEncoder passwordEncoder;
    
    // Spring Security认证管理器，用于用户名密码认证
    private final AuthenticationManager authenticationManager;
    
    // JWT工具类，用于令牌的生成和验证
    private final JwtUtils jwtUtils;
    
    // 用户详情服务，用于加载用户信息
    private final UserDetailsServiceImpl userDetailsService;
    
    /**
     * 用户注册业务逻辑
     * 
     * 处理用户注册的完整流程：
     * 1. 验证注册信息
     * 2. 检查用户名和邮箱唯一性
     * 3. 创建用户记录
     * 4. 设置默认角色
     * 5. 生成JWT令牌
     * 6. 返回认证响应
     * 
     * @param request 用户注册请求对象
     * @return AuthResponse 包含令牌和用户信息的认证响应
     * @throws BusinessException 业务异常，如用户名已存在、邮箱已被使用等
     */
    @Transactional
    public AuthResponse register(RegisterRequest request) {
        // 1. 记录注册请求的日志
        System.out.println("用户注册请求: " + request.getUsername());
        
        // 2. 验证确认密码是否与密码一致
        if (!request.getPassword().equals(request.getConfirmPassword())) {
            throw new BusinessException("两次输入的密码不一致");
        }
        
        // 3. 检查用户名是否已存在
        // Optional是Java 8引入的容器类，用于处理可能为null的值
        if (userRepository.findByUsername(request.getUsername()).isPresent()) {
            throw new BusinessException("用户名已存在");
        }
        
        // 4. 检查邮箱是否已被使用
        if (userRepository.findByEmail(request.getEmail()).isPresent()) {
            throw new BusinessException("邮箱已被使用");
        }
        
        // 5. 检查用户是否同意了用户协议
        if (!Boolean.TRUE.equals(request.getAgreeToTerms())) {
            throw new BusinessException("请同意用户协议");
        }
        
        // 6. 创建用户实体对象
        User user = new User();
        user.setUsername(request.getUsername());  // 设置用户名
        user.setEmail(request.getEmail());        // 设置邮箱
        // 使用BCrypt加密密码，不存储明文密码
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setEnabled(true);                    // 设置用户状态为启用
        user.setEmailVerified(false);             // 默认邮箱未验证
        
        // 7. 设置默认角色
        // 查找系统中的"USER"角色
        Optional<Role> userRole = roleRepository.findByRoleName("USER");
        if (userRole.isPresent()) {
            // 如果找到了USER角色，将其添加到用户的角色集合中
            user.getRoles().add(userRole.get());
        } else {
            // 如果没有找到USER角色，说明系统配置有问题
            System.out.println("默认用户角色不存在，需要手动创建");
            throw new BusinessException("系统配置错误，请联系管理员");
        }
        
        // 8. 保存用户到数据库
        // 数据库触发器会自动创建用户资料和悄悄话设置
        user = userRepository.save(user);
        
        // 9. 更新用户资料中的昵称
        // 由于数据库触发器已经创建了用户资料，这里只需要更新昵称
        if (request.getNickname() != null && !request.getNickname().trim().isEmpty()) {
            // 查找触发器创建的用户资料
            UserProfile profile = userProfileRepository.findByUserId(user.getId())
                    .orElse(null);
            if (profile != null) {
                profile.setNickname(request.getNickname());
                userProfileRepository.save(profile);
            }
        }
        
        // 10. 生成JWT令牌
        // 创建用户详情对象
        UserDetails userDetails = UserDetailsImpl.create(user);
        
        // 生成访问令牌（包含用户ID和角色信息）
        String accessToken = generateAccessToken(userDetails, user);
        
        // 生成刷新令牌
        String refreshToken = jwtUtils.generateRefreshToken(userDetails);
        
        // 11. 记录注册成功的日志
        System.out.println("用户注册成功: " + user.getUsername());
        
        // 12. 构建并返回认证响应
        return buildAuthResponse(accessToken, refreshToken, user);
    }
    
    /**
     * 用户登录业务逻辑
     * 
     * 处理用户登录的完整流程：
     * 1. 验证用户存在性和状态
     * 2. 使用Spring Security进行身份验证
     * 3. 更新最后登录时间
     * 4. 生成JWT令牌
     * 5. 返回认证响应
     * 
     * @param request 用户登录请求对象
     * @return AuthResponse 包含令牌和用户信息的认证响应
     * @throws BusinessException 业务异常，如用户不存在、密码错误、账号被禁用等
     */
    @Transactional
    public AuthResponse login(LoginRequest request) {
        // 1. 记录登录请求的日志
        System.out.println("用户登录请求: " + request.getUsernameOrEmail());
        
        try {
            // 2. 首先检查用户是否存在且状态正常
            // 支持使用用户名或邮箱登录
            User user = userRepository.findByUsernameOrEmail(request.getUsernameOrEmail(), request.getUsernameOrEmail())
                    .orElseThrow(() -> new BusinessException("用户名或密码错误"));
            
            // 3. 检查用户账号状态
            if (!user.isEnabled()) {
                System.out.println("用户账号已被禁用: " + user.getUsername());
                throw new BusinessException("账号已被禁用，请联系管理员");
            }
            
            // 4. 使用Spring Security的认证管理器进行身份验证
            // 创建认证令牌对象
            Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                    request.getUsernameOrEmail(),  // 用户名或邮箱
                    request.getPassword()          // 密码
                )
            );
            
            // 5. 如果认证成功，获取用户详情
            UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
            
            // 6. 更新用户的最后登录时间
            user.setLastLoginTime(LocalDateTime.now());
            userRepository.save(user);
            
            // 7. 生成JWT令牌
            String accessToken = generateAccessToken(userDetails, user);
            String refreshToken = jwtUtils.generateRefreshToken(userDetails);
            
            // 8. 记录登录成功的日志
            System.out.println("用户登录成功: " + user.getUsername());
            
            // 9. 构建并返回认证响应
            return buildAuthResponse(accessToken, refreshToken, user);
            
        } catch (BusinessException e) {
            // 10. 重新抛出业务异常（不改变异常类型）
            throw e;
        } catch (Exception e) {
            // 11. 捕获其他异常（如认证失败），统一处理为登录失败
            System.out.println("用户登录失败: " + request.getUsernameOrEmail() + ", 原因: " + e.getMessage());
            throw new BusinessException("用户名或密码错误");
        }
    }
    
    /**
     * 刷新JWT令牌业务逻辑
     * 
     * 处理令牌刷新的完整流程：
     * 1. 验证刷新令牌有效性
     * 2. 从刷新令牌中提取用户信息
     * 3. 检查用户状态
     * 4. 生成新的访问令牌
     * 5. 返回认证响应
     * 
     * @param request 刷新令牌请求对象
     * @return AuthResponse 包含新令牌和用户信息的认证响应
     * @throws BusinessException 业务异常，如令牌无效、用户被禁用等
     */
    public AuthResponse refreshToken(RefreshTokenRequest request) {
        // 1. 记录刷新令牌请求的日志
        System.out.println("刷新令牌请求");
        
        try {
            // 2. 验证刷新令牌的有效性
            if (!jwtUtils.validateToken(request.getRefreshToken())) {
                throw new BusinessException("刷新令牌无效");
            }
            
            // 3. 从刷新令牌中提取用户名
            String username = jwtUtils.getUsernameFromToken(request.getRefreshToken());
            
            // 4. 根据用户名加载用户详情
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UserDetailsImpl userDetailsImpl = (UserDetailsImpl) userDetails;
            User user = userDetailsImpl.getUser();
            
            // 5. 检查用户状态是否正常
            if (!user.isEnabled()) {
                System.out.println("用户账号已被禁用，无法刷新令牌: " + username);
                throw new BusinessException("账号已被禁用，请重新登录");
            }
            
            // 6. 生成新的访问令牌
            String newAccessToken = generateAccessToken(userDetails, user);
            
            // 7. 记录令牌刷新成功的日志
            System.out.println("令牌刷新成功: " + username);
            
            // 8. 构建并返回认证响应
            // 注意：这里复用了原来的刷新令牌，只更新了访问令牌
            return buildAuthResponse(newAccessToken, request.getRefreshToken(), user);
            
        } catch (BusinessException e) {
            // 9. 重新抛出业务异常
            throw e;
        } catch (Exception e) {
            // 10. 捕获其他异常，统一处理为刷新失败
            System.out.println("令牌刷新失败: " + e.getMessage());
            throw new BusinessException("令牌刷新失败");
        }
    }
    
    /**
     * 修改密码业务逻辑
     * 
     * 处理用户修改密码的完整流程：
     * 1. 获取当前登录用户
     * 2. 验证旧密码
     * 3. 加密新密码
     * 4. 更新用户密码
     * 
     * @param request 密码修改请求对象
     * @throws BusinessException 业务异常，如旧密码错误等
     */
    @Transactional
    public void changePassword(PasswordChangeRequest request) {
        // 1. 获取当前登录用户的ID
        Long currentUserId = SecurityUtils.getCurrentUserId();
        if (currentUserId == null) {
            throw new BusinessException("请先登录");
        }
        
        // 2. 查找当前用户
        User user = userRepository.findById(currentUserId)
                .orElseThrow(() -> new BusinessException("用户不存在"));
        
        // 3. 验证旧密码是否正确
        if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) {
            throw new BusinessException("旧密码错误");
        }
        
        // 4. 验证新密码和确认密码是否一致
        if (!request.getNewPassword().equals(request.getConfirmPassword())) {
            throw new BusinessException("两次输入的新密码不一致");
        }
        
        // 5. 加密新密码并更新
        user.setPassword(passwordEncoder.encode(request.getNewPassword()));
        userRepository.save(user);
        
        // 6. 记录密码修改成功的日志
        System.out.println("用户密码修改成功: " + user.getUsername());
    }
    
    /**
     * 登出
     */
    public void logout() {
        System.out.println("用户登出");
        SecurityContextHolder.clearContext();
    }
    
    /**
     * 获取当前用户信息
     */
    public AuthResponse.UserInfo getCurrentUser() {
        UserDetailsImpl userDetails = SecurityUtils.getCurrentUserDetails();
        if (userDetails == null) {
            throw new BusinessException("用户未登录");
        }
        
        User user = userDetails.getUser();
        return buildUserInfo(user);
    }
    
    /**
     * 验证用户名是否可用
     * 
     * 检查指定的用户名是否已被其他用户使用
     * 
     * @param username 要检查的用户名
     * @return boolean true表示可用，false表示已被使用
     */
    public boolean isUsernameAvailable(String username) {
        // 查询数据库中是否存在该用户名
        // !表示逻辑非，如果不存在则表示可用
        return !userRepository.findByUsername(username).isPresent();
    }
    
    /**
     * 验证邮箱是否可用
     * 
     * 检查指定的邮箱是否已被其他用户使用
     * 
     * @param email 要检查的邮箱
     * @return boolean true表示可用，false表示已被使用
     */
    public boolean isEmailAvailable(String email) {
        // 查询数据库中是否存在该邮箱
        return !userRepository.findByEmail(email).isPresent();
    }
    
    /**
     * 生成访问令牌
     * 
     * 创建包含用户ID和角色信息的访问令牌
     * 
     * @param userDetails 用户详情对象
     * @param user 用户实体对象
     * @return String 生成的访问令牌
     */
    private String generateAccessToken(UserDetails userDetails, User user) {
        // 1. 创建额外的声明映射
        Map<String, Object> extraClaims = new HashMap<>();
        
        // 2. 添加用户ID到声明中
        extraClaims.put("userId", user.getId());
        
        // 3. 添加用户角色到声明中
        // 使用Stream API将角色集合转换为逗号分隔的字符串
        extraClaims.put("role", user.getRoles().stream()
                .map(Role::getRoleName)
                .collect(Collectors.joining(",")));
        
        // 4. 生成并返回令牌
        return jwtUtils.generateToken(userDetails, extraClaims);
    }
    
    /**
     * 构建认证响应对象
     * 
     * 创建包含令牌和用户信息的认证响应
     * 
     * @param accessToken 访问令牌
     * @param refreshToken 刷新令牌
     * @param user 用户实体对象
     * @return AuthResponse 认证响应对象
     */
    private AuthResponse buildAuthResponse(String accessToken, String refreshToken, User user) {
        // 使用建造者模式构建认证响应对象
        return AuthResponse.builder()
                .accessToken(accessToken)                                    // 访问令牌
                .refreshToken(refreshToken)                                  // 刷新令牌
                .tokenType("Bearer")                                         // 令牌类型
                .expiresIn(jwtUtils.getTokenRemainingTime(accessToken) / 1000) // 过期时间（秒）
                .userInfo(buildUserInfo(user))                               // 用户信息
                .build();
    }
    
    /**
     * 构建用户信息对象
     * 
     * 从用户实体对象中提取需要返回给前端的用户信息
     * 
     * @param user 用户实体对象
     * @return AuthResponse.UserInfo 用户信息对象
     */
    private AuthResponse.UserInfo buildUserInfo(User user) {
        // 1. 提取用户角色集合
        Set<String> roles = user.getRoles().stream()
                .map(Role::getRoleName)
                .collect(Collectors.toSet());
        
        // 2. 使用建造者模式构建用户信息对象
        return AuthResponse.UserInfo.builder()
                .id(user.getId())                                            // 用户ID
                .username(user.getUsername())                                // 用户名
                .email(user.getEmail())                                      // 邮箱
                .nickname(user.getProfile() != null ? user.getProfile().getNickname() : null) // 昵称
                .avatar(user.getAvatar())                                    // 头像
                .roles(roles)                                                // 角色集合
                .emailVerified(user.isEmailVerified())                       // 邮箱验证状态
                .lastLoginTime(user.getLastLoginTime())                      // 最后登录时间
                .build();
    }
} 